﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using K9Nano.Dependency;
using K9Nano.Remoting;
using K9Nano.Share.Exceptions;
using K9Nano.Share.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace K9Nano.Events
{
    public class EventBus : IEventBus, ISingletonDependency
    {
        protected readonly IServiceProvider ServiceProvider;

        protected readonly IApiInfo Source;
        protected readonly ConcurrentDictionary<string, List<Type>> Factories;
        protected ActionBlock<EventDataWrapper> EventBlock { get; set; }
        protected readonly ILogger Logger;
        protected readonly ITypeEventNameGenerator TypeEventNameGenerator;

        public EventBus(IServiceProvider serviceProvider)
        {
            ServiceProvider = serviceProvider;
            Source = serviceProvider.GetRequiredService<IApiInfo>(); ;
            Logger = serviceProvider.GetRequiredService<ILogger<EventBus>>();
            TypeEventNameGenerator = serviceProvider.GetRequiredService<ITypeEventNameGenerator>();
            Factories = new ConcurrentDictionary<string, List<Type>>();
            EventBlock = new ActionBlock<EventDataWrapper>(Fire);
        }

        public void Register<TEventData, THandler>() where TEventData : IEventData where THandler : IEventHandler<TEventData>
        {
            var eventName = TypeEventNameGenerator.Generate(typeof(TEventData));
            ((IEventBus)this).Register<TEventData, THandler>(eventName);
        }

        public void AsyncRegister<TEventData, THandler>() where TEventData : IEventData where THandler : IAsyncEventHandler<TEventData>
        {
            var eventName = TypeEventNameGenerator.Generate(typeof(TEventData));
            ((IEventBus)this).AsyncRegister<TEventData, THandler>(eventName);
        }

        public virtual void Register(string eventName, Type handlerType)
        {
            ValidateHandler(handlerType);

            Factories.AddOrUpdate(eventName,
                _ => new List<Type> { handlerType },
                (_, types) =>
                {
                    if (types.Contains(handlerType))
                    {
                        throw new ArgumentException($"{eventName} has already a handler of {handlerType}");
                    }
                    types.Add(handlerType);
                    return types;
                });
        }

        public void Trigger<TEventData>(TEventData eventData, bool internalOnly = false) where TEventData : IEventData
        {
            var eventName = TypeEventNameGenerator.Generate(typeof(TEventData));
            Trigger(eventName, eventData, internalOnly);
        }

        public void Trigger(Type eventType, IEventData eventData, bool internalOnly = false)
        {
            var eventName = TypeEventNameGenerator.Generate(eventType);
            Trigger(eventName, eventData, internalOnly);
        }

        public ValueTask TriggerAsync<TEventData>(TEventData eventData, bool internalOnly = false) where TEventData : IEventData
        {
            var eventName = TypeEventNameGenerator.Generate(typeof(TEventData));
            return TriggerAsync(eventName, eventData, internalOnly);
        }

        public ValueTask TriggerAsync(Type eventType, IEventData eventData, bool internalOnly = false)
        {
            var eventName = TypeEventNameGenerator.Generate(eventType);
            return TriggerAsync(eventName, eventData, internalOnly);
        }

        public virtual void Trigger(string eventName, IEventData eventData, bool internalOnly)
        {
            EventBlock.Post(new EventDataWrapper(eventName, eventData, Source));
        }

        public virtual ValueTask TriggerAsync(string eventName, IEventData eventData, bool internalOnly)
        {
            Trigger(eventName, eventData, internalOnly);
            return new ValueTask();
        }

        public virtual async Task Fire(EventDataWrapper data)
        {
            Logger.LogDebug($"EventBus:Fire: {data}");

            if (Factories.TryGetValue(data.Name, out var types))
            {
                foreach (var type in types)
                {
                    try
                    {
                        if (IsEventHandler(type))
                        {
                            TriggerHandlingException(type, data.Data);
                        }
                        else  // async: checked when register
                        {
                            await TriggerAsyncHandlingException(type, data.Data);
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError(ex, "Error has occurred while triggering the event: " + data.Name);
                    }
                }
            }
            else
            {
                Logger.LogDebug("There is no Handler for event: " + data.Name);
            }
        }

        protected virtual void ValidateHandler(Type handler)
        {
            if (!IsEventHandler(handler) && !IsAsyncEventHandler(handler))
            {
                throw new ArgumentException($"{handler.FullName} must inherits from IEventHandler<T> or IAsyncEventHandler<T>.");
            }
        }

        protected virtual void TriggerHandlingException(Type handlerType,
            IEventData eventData)
        {
            var scope = ServiceProvider.CreateScope();
            try
            {
                var handler = scope.ServiceProvider.GetService(handlerType);
                if (handler == null)
                {
                    throw new K9Exception($"There is no handler registered for {handlerType}");
                }
                var method = handlerType.GetMethod("HandleEvent");

                method.Invoke(handler, new object[] { eventData });
            }
            catch (TargetInvocationException ex)
            {
                ex.InnerException.ReThrow();
            }
            finally
            {
                scope.Dispose();
            }
        }

        protected virtual async Task TriggerAsyncHandlingException(Type handlerType,
            IEventData eventData)
        {
            var scope = ServiceProvider.CreateScope();
            try
            {
                var handler = scope.ServiceProvider.GetService(handlerType);
                if (handler == null)
                {
                    throw new K9Exception($"There is no handler registered for {handlerType}");
                }
                var method = handlerType.GetMethod("HandleEventAsync");

                await (Task)method.Invoke(handler, new object[] { eventData });
            }
            catch (TargetInvocationException ex)
            {
                ex.InnerException.ReThrow();
            }
            finally
            {
                scope.Dispose();
            }
        }

        protected virtual bool IsEventHandler(Type handlerType)
        {
            return handlerType.GetInterfaces()
                .Where(i => i.IsGenericType)
                .Any(i => i.GetGenericTypeDefinition() == typeof(IEventHandler<>));
        }

        protected virtual bool IsAsyncEventHandler(Type handlerType)
        {
            return handlerType.GetInterfaces()
                .Where(i => i.IsGenericType)
                .Any(i => i.GetGenericTypeDefinition() == typeof(IAsyncEventHandler<>));
        }
    }
}